; vesa.asm - 16bit (vm86 mode) VESA service
;
; Implements a message queue interface to the VESA VBE BIOS interface.
; The message API supports all major VBE services up to version 3.0. Refer to
; vbe/vbe.h for structure definitions for request and response formats.
;
;  Copyright 2006 Corey Tabaka
;
;  Licensed under the Apache License, Version 2.0 (the "License");
;  you may not use this file except in compliance with the License.
;  You may obtain a copy of the License at
;
;      http://www.apache.org/licenses/LICENSE-2.0
;
;  Unless required by applicable law or agreed to in writing, software
;  distributed under the License is distributed on an "AS IS" BASIS,
;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;  See the License for the specific language governing permissions and
;  limitations under the License.
;


org 0x100

section .text
bits 16

%define putc_xy(x, y, c) mov word [gs:y * 80 * 2 + x * 2], c
%define screen(x, y) (y * 80 * 2 + x * 2)

; return values for AL
%define VBE_FUNCTION_SUPPORTED 4fh

; return values for AH
%define VBE_SUCCESS		00h
%define VBE_FAILED		01h
%define VBE_UNSUPPORTED	02h
%define VBE_INVALID		03h

%define APP_ERROR		0ffh

%define SYSCALL_MESSAGE 4
%define SYSCALL_MESSAGE_SEND_PULSE 0
%define SYSCALL_MESSAGE_RECEIVE_PULSE 1
%define SYSCALL_MESSAGE_SEND 2
%define SYSCALL_MESSAGE_RECEIVE 3
%define SYSCALL_MESSAGE_REPLY 4
%define SYSCALL_MESSAGE_PIPE_CREATE 5
%define SYSCALL_MESSAGE_PIPE_DESTROY 6

struc message_t
	function: resb 1
	subfunction: resw 1
endstruc

struc vbe_info_t
	vbe_signature:				resb 4
	vbe_version:				resw 1
	vbe_oem_string_ptr:			resd 1
	vbe_capabilities:			resd 1
	vbe_video_mode_ptr:			resd 1
	vbe_total_memory:			resw 1
	vbe_oem_software_rev:		resw 1
	vbe_oem_vendor_name_ptr:	resd 1
	vbe_oem_product_name_ptr:	resd 1
	vbe_oem_product_rev_ptr:	resd 1
	vbe_reserved:				resb 222
	vbe_oem_data:				resb 256
endstruc

start:
	mov ax,0xb800
	mov gs,ax
	putc_xy(79, 10, 0x0200 | '!')
	mov ax,0x1234
	
	; create pipe
	push dword SYSCALL_MESSAGE_PIPE_CREATE
	push dword SYSCALL_MESSAGE
	int 30h
	add sp,8
	mov [pipe],eax
	
	jmp read_message

	mov ecx,0x1000
.loop:
	mov di,screen(71, 10)
	mov edx,ecx
	call print_hex16
	pause
	a32 loop .loop

read_message:	
	push dword message_len
	lea eax,[message]
	push eax
	push dword [pipe]					; qid
	push dword SYSCALL_MESSAGE_RECEIVE
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1	; exit if fail
	je near done
	
	call stack_clean
	
	; advance the request count
	cld
	inc dword [requests]
	mov di,screen(71, 10)
	mov edx,[requests]
	call print_hex16
	
	; reset es
	mov bx,ds
	mov es,bx
	
	; setup vesa request
	mov ah,4fh
	mov al,[message_fn]
	
	; disbatch the selected function
	cmp al,00h
	je near controller_info
	cmp al,01h
	je near mode_info
	cmp al,02h
	je near set_mode
	cmp al,03h
	je near get_mode
	cmp al,04h
	je near save_restore_state
	cmp al,05h
	je near window_control
	;cmp al,06h
	;je near set_get_logical_sll
	cmp al,07h
	je near set_get_display_start
	;cmp al,08h
	;je near set_get_dac_palette_format
	;cmp al,09h
	;je near set_get_palette_data
	;cmp al,0ah
	;je near get_set_pixel_clock
	cmp al,15h
	je near ddc_extension
	
	mov ah,APP_ERROR				; else
	jmp error_reply
	
controller_info:
	mov dword [buffer],'VBE2'
	mov di,buffer
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	
	;jmp .ci_end4
	
	; patch up the pointers returned by VBE if they reside in the data area
	; otherwise, copy them to the data area and set the pointers
	
	; data area offset for OEM strings
	xor edx,edx
	
	; check that the segment is 0
	cmp word [buffer+vbe_video_mode_ptr+2],0
	jne .ci_next0
	; check that the offset is greater than 0x7c00 (code start)
	cmp word [buffer+vbe_video_mode_ptr],0x7c00
	jle .ci_next0
	; check that the offset is less than 0xffff
	cmp word [buffer+vbe_video_mode_ptr],0xffff
	jg .ci_next0
	; perform the offset conversion
	sub word [buffer+vbe_video_mode_ptr],buffer+vbe_reserved
	jmp .ci_end0
	
.ci_next0:
	; copy the mode numbers into the reserved data area
	; setup es:si = VBE Video Mode list address
	mov ax,[buffer+vbe_video_mode_ptr+2]
	mov es,ax
	mov si,[buffer+vbe_video_mode_ptr]
	; setup ds:di = VBE Reserved data area
	mov di,buffer+vbe_reserved
	; update VBE Video Mode Pointer
	mov dword [buffer+vbe_video_mode_ptr],0
.ci_loop0:
	mov ax,[es:si]
	mov [ds:di],ax
	add si,2
	add di,2
	cmp ax,0xffff
	jne .ci_loop0
	
.ci_end0:
	; check that the segment is 0
	cmp word [buffer+vbe_oem_string_ptr+2],0
	jne .ci_next1
	; check that the offset is greater than 0x7c00 (code start)
	cmp word [buffer+vbe_oem_string_ptr],0x7c00
	jle .ci_next1
	; check that the offset is less than 0xffff
	cmp word [buffer+vbe_oem_string_ptr],0xffff
	jg .ci_next1
	; perform the offset conversion
	sub word [buffer+vbe_oem_string_ptr],buffer+vbe_oem_data
	jmp .ci_end1
	
.ci_next1:
	; copy string into VBE OEM Data area
	; setup es:si = OEM String address
	mov ax,[buffer+vbe_oem_string_ptr+2]
	mov es,ax
	mov si,[buffer+vbe_oem_string_ptr]
	; dx = data area offset for this string
	; setup ds:di = OEM Data area + dx
	mov di,buffer+vbe_oem_data
	add di,dx
	cld
	; update OEM String pointer
	mov dword [buffer+vbe_oem_string_ptr],edx
	
.ci_loop1:
	mov al,[es:si]
	mov [ds:di],al
	inc si
	inc di
	inc dx
	cmp al,0
	jne .ci_loop1

.ci_end1:
	; check that the segment is 0
	cmp word [buffer+vbe_oem_vendor_name_ptr+2],0
	jne .ci_next2
	; check that the offset is greater than 0x7c00 (code start)
	cmp word [buffer+vbe_oem_vendor_name_ptr],0x7c00
	jle .ci_next2
	; check that the offset is less than 0xffff
	cmp word [buffer+vbe_oem_vendor_name_ptr],0xffff
	jg .ci_next2
	; perform the offset conversion
	sub word [buffer+vbe_oem_vendor_name_ptr],buffer+vbe_oem_data
	jmp .ci_end2
	
.ci_next2:
	; copy string into VBE OEM Data area
	; setup es:si = OEM Vendor Name address
	mov ax,[buffer+vbe_oem_vendor_name_ptr+2]
	mov es,ax
	mov si,[buffer+vbe_oem_vendor_name_ptr]
	; dx = data area offset for this string
	; setup ds:di = OEM Data area + dx
	mov di,buffer+vbe_oem_data
	add di,dx
	cld
	; update OEM Vendor Name pointer
	mov dword [buffer+vbe_oem_vendor_name_ptr],edx
	
.ci_loop2:
	mov al,[es:si]
	mov [ds:di],al
	inc si
	inc di
	inc dx
	cmp al,0
	jne .ci_loop2

.ci_end2:
	; check that the segment is 0
	cmp word [buffer+vbe_oem_product_name_ptr+2],0
	jne .ci_next3
	; check that the offset is greater than 0x7c00 (code start)
	cmp word [buffer+vbe_oem_product_name_ptr],0x7c00
	jle .ci_next3
	; check that the offset is less than 0xffff
	cmp word [buffer+vbe_oem_product_name_ptr],0xffff
	jg .ci_next3
	; perform the offset conversion
	sub word [buffer+vbe_oem_product_name_ptr],buffer+vbe_oem_data
	jmp .ci_end3
	
.ci_next3:
	; copy string into VBE OEM Data area
	; setup es:si = OEM Vendor Name address
	mov ax,[buffer+vbe_oem_product_name_ptr+2]
	mov es,ax
	mov si,[buffer+vbe_oem_product_name_ptr]
	; dx = data area offset for this string
	; setup ds:di = OEM Data area + dx
	mov di,buffer+vbe_oem_data
	add di,dx
	cld
	; update OEM Product Name pointer
	mov dword [buffer+vbe_oem_product_name_ptr],edx
	
.ci_loop3:
	mov al,[es:si]
	mov [ds:di],al
	inc si
	inc di
	inc dx
	cmp al,0
	jne .ci_loop3

.ci_end3:
	; check that the segment is 0
	cmp word [buffer+vbe_oem_product_rev_ptr+2],0
	jne .ci_next4
	; check that the offset is greater than 0x7c00 (code start)
	cmp word [buffer+vbe_oem_product_rev_ptr],0x7c00
	jle .ci_next4
	; check that the offset is less than 0xffff
	cmp word [buffer+vbe_oem_product_rev_ptr],0xffff
	jg .ci_next4
	; perform the offset conversion
	sub word [buffer+vbe_oem_product_rev_ptr],buffer+vbe_oem_data
	jmp .ci_end4
	
.ci_next4:
	; copy string into VBE OEM Data area
	; setup es:si = OEM Vendor Name address
	mov ax,[buffer+vbe_oem_product_rev_ptr+2]
	mov es,ax
	mov si,[buffer+vbe_oem_product_rev_ptr]
	; dx = data area offset for this string
	; setup ds:di = OEM Data area + dx
	mov di,buffer+vbe_oem_data
	add di,dx
	cld
	; update OEM Product Name pointer
	mov dword [buffer+vbe_oem_product_rev_ptr],edx
	
.ci_loop4:
	mov al,[es:si]
	mov [ds:di],al
	inc si
	inc di
	inc dx
	cmp al,0
	jne .ci_loop4

.ci_end4:
	
	push dword response_len
	lea eax,[response]
	push eax
	push dword [pipe]					; qid
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message
	
mode_info:
	mov cx,[message_param]
	mov di,buffer
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	
	push dword response_len-256
	lea eax,[response]
	push eax
	push dword [pipe]
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message

set_mode:
	mov bx,[message_param]
	mov di,message_param+2
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	
	push dword 1
	lea eax,[response]
	push eax
	push dword [pipe]
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message
	
get_mode:
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	mov [buffer],bx
	
	push dword 3
	lea eax,[response]
	push eax
	push dword [pipe]
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message

save_restore_state:
	mov dl,[message_param]		; 16bit aligned
	mov cx,[message_param+2]
	mov bx,message_param+4
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	mov [buffer],bx
	
	push dword 3
	lea eax,[response]
	push eax
	push dword [pipe]
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message

window_control:
	mov bx,[message_param]
	mov dx,[message_param+2]
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	mov [buffer],dx
	
	push dword 3
	lea eax,[response]
	push eax
	push dword [pipe]
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message

set_get_display_start:
	xor bh,bh						; reserved
	mov bl,[message_param]
	mov ecx,[message_param+1]
	mov edx,[message_param+5]
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	mov [buffer],bh
	mov [buffer+1],cx
	mov [buffer+3],dx
	
	push dword 6
	lea eax,[response]
	push eax
	push dword [pipe]
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message

ddc_extension:
	mov bl,[message_param]
	mov cx,[message_param+1]
	
	cmp bl,0						; select subfunction
	jne near .read_edid
	
									; report vbe/ddc capabilities
	xor di,di						; es:di = 00:00
	mov es,di
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	mov [buffer],bl
	mov [buffer+1],bh
	
	push dword 3
	jmp .continue

.read_edid:							; get edid block
	mov di,buffer					; es:di = ds:buffer (es set to ds at top)
	mov dx,[message_param+2]
	int 10h
	
	cmp ax,(VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED
	jne near error_reply			; error
	
	mov [return_code],ah
	
	push dword 129

.continue							; complete the syscall params started above
	lea eax,[response]
	push eax
	push dword [pipe]
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message

error_reply:
	mov [return_code],ah
	
	push dword 1
	lea eax,[response]
	push eax
	push dword [pipe]
	push dword SYSCALL_MESSAGE_REPLY
	push dword SYSCALL_MESSAGE
	int 30h
	
	cmp eax,-1
	je near done
	
	call stack_clean
	
	jmp read_message
	
done:	
	ret				; terminate

stack_clean:
	pop bx			; save ret addr
	pop eax
	pop eax
	pop eax
	pop eax
	pop eax
	push bx
	
	ret

; hex print routine put value in edx, put starting screen position in di
print_hex16:
	push gs
	push cx
	push ax
	push bx
	push edx
	
	mov ax,0xb800		; setup video selector
	mov gs,ax
	
	and di,0xfffe		; make sure screen offset is on a two byte boundry
	
	mov cx,8			; print out 8 chars
.loop:
	rol edx,4			; rotate upper byte around to lowest nibble
	mov ax, 0x070f		; ah = attribute, al = digit mask
	and al, dl			; mask digit into al
	add al, 0x90		; convert al to ascii hex with next four instructions
	daa
	adc al, 0x40
	daa
	mov word [gs:di], ax	; write char
	add di, 2			; advance index/column
	loop .loop
	
	pop edx
	pop bx
	pop ax
	pop cx
	pop gs
	ret

section .data
align 4

pipe:
	dd 1

requests:
	dd 0

;section .bss
;align 4

; message
message:
message_fn:
	resb 1
message_param:
	resb 512
message_len equ $-message

; info block
response:
return_code:
	resb 1
buffer:
	resb 512
response_len equ $-response
